using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using InputEventSystem;


namespace XNAViewerSample
{
    #region Delegates
    public delegate void CameraHandler(GameTime time, Keys key);
    #endregion

    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class CameraComponent : Microsoft.Xna.Framework.GameComponent
    {
        #region Fields
        private float cameraArc;
        private float cameraRotation;
        private float cameraDistance;
        private float cameraHeight;
        private float cameraFOV;
        private Vector3 cameraPosition;
        private Vector3 cameraTarget;

        private Matrix ViewMatrix;
        private Matrix ProjectionMatrix;

        private GraphicsDevice Device;
        private InputEvents input;
        #endregion

        #region Events
        public event CameraHandler cameraHandler;
        private Keys keyPressed;
        private Keys[] validKeys = {   //Keys.Up,
                                       //Keys.Down,
                                       //Keys.Right,
                                       //Keys.Left,
                                       Keys.W,
                                       Keys.A,
                                       Keys.S,
                                       Keys.D,
                                       Keys.Q,
                                       Keys.E,
                                       Keys.X,
                                       Keys.Z,
                                       Keys.C
                                   };
        #endregion

        public CameraComponent(Game game)
            : base(game)
        {
            // TODO: Construct any child components here
            Device = game.GraphicsDevice;

            cameraArc = 0;
            cameraRotation = 0;
            cameraDistance = 0;
            cameraHeight = 0;
            cameraFOV = MathHelper.PiOver4;
            cameraPosition = Vector3.Zero;
            cameraTarget = Vector3.Zero;
            input = null;
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            // TODO: Add your initialization code here
            keyPressed = Keys.Escape;
            base.Initialize();
        }

        public InputEvents Controls
        {
            set 
            {
                Debug.Assert(value != null);
                input = value;
                input.KeyDown += new KeyDownHandler(input_KeyDown);
            }
        }

        public Matrix View
        {
            get
            {
                ViewMatrix = Matrix.Identity *
                          Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation)) *
                          Matrix.CreateRotationX(MathHelper.ToRadians(cameraArc)) *
                          Matrix.CreateLookAt(cameraPosition + new Vector3(0, 0, cameraDistance) + new Vector3(0, cameraHeight, 0), 
                                cameraTarget + new Vector3(0, cameraHeight, 0), Vector3.Up);
                return ViewMatrix;
            }
        }

        public Matrix Projection
        {
            get { return ProjectionMatrix; }
        }

        private void input_KeyDown(KeyEventArgs args, GameTime time)
        {
            keyPressed = args.Key;
        }

        public float Arc
        {
            get { return cameraArc; }
            set { cameraArc = MathHelper.ToDegrees(MathHelper.WrapAngle(MathHelper.ToRadians(value))); }
        }

        public float Rotation
        {
            get { return cameraRotation; }
            set { cameraRotation = MathHelper.ToDegrees(MathHelper.WrapAngle(MathHelper.ToRadians(value))); }
        }

        public float Distance
        {
            get { return cameraDistance; }
            set { cameraDistance = value; }
        }

        public float Height
        {
            get { return cameraHeight; }
            set { cameraHeight = value; }
        }

        public float FoV
        {
            get { return cameraFOV; }
            set { cameraFOV = value; }
        }

        public Vector3 Position
        {
            get { return cameraPosition; }
            set { cameraPosition = value; }
        }

        public Vector3 AbsPosition
        {
            get { return cameraPosition + new Vector3(0, 0, cameraDistance) + new Vector3(0, cameraHeight, 0); }
        }

        public Vector3 Target
        {
            get { return cameraTarget; }
            set { cameraTarget = value; }
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            // TODO: Add your update code here
            if (cameraHandler != null)
            {
                // fire only on camera control keys
                if (validKeys.Contains(keyPressed))
                    cameraHandler(gameTime, keyPressed);
                // assign to a non-valid key
                keyPressed = Keys.Escape;
            }

            float aspectRatio = (float)Device.Viewport.Width / (float)Device.Viewport.Height;
            Vector3 position = cameraPosition + new Vector3(0, 0, cameraDistance) + new Vector3(0, cameraHeight, 0);
            Vector3 target = cameraTarget + new Vector3(0, cameraHeight, 0);
            ViewMatrix = Matrix.Identity *
                          Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation)) *
                          Matrix.CreateRotationX(MathHelper.ToRadians(cameraArc)) *
                          Matrix.CreateLookAt(position, target, Vector3.Up);
            ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(cameraFOV,
                                                                    aspectRatio,
                                                                    10.0f,
                                                                    5000.0f);
            base.Update(gameTime);
        }
    }
}